package top.codef.sqlfilter;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;

import jakarta.persistence.criteria.CommonAbstractCriteria;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import top.codef.dao.DaoUtils;
import top.codef.enums.OrderEnum;
import top.codef.exceptions.JpaAmebaException;
import top.codef.sqlfilter.annotations.SubQuery;
import top.codef.sqlfilter.annotations.WhereCondition;
import top.codef.sqlfilter.functional.FilterCondition;
import top.codef.sqlfilter.functional.GroupCondition;
import top.codef.sqlfilter.functional.JoinCondition;
import top.codef.sqlfilter.functional.OrderCondition;
import top.codef.sqlfilter.functional.SelectCondition;

public class CommonFilter {

	private static final Log logger = LogFactory.getLog(CommonFilter.class);

	protected final List<Element<? extends Object>> updatableList = new LinkedList<>();

	protected final List<FilterCondition> list = new LinkedList<>();

	protected final List<JoinCondition> joinList = new LinkedList<>();

	protected final List<OrderCondition> orderList = new LinkedList<>();

	protected final List<SelectCondition> selectors = new LinkedList<>();

	protected final List<GroupCondition> groupingBy = new LinkedList<>();

	protected final Map<String, SubCommonFilter<?, ?>> subQuery = new HashMap<>();

	protected Integer limitCount;

	protected Integer limitStart;

	protected String countField;

	public CommonFilter() {
	}

	protected <T> boolean checkNotNull(T value) {
		return value != null;
	}

	public Map<String, SubCommonFilter<?, ?>> getSubQuery() {
		return subQuery;
	}

	public void setCountField(String countField) {
		this.countField = countField;
	}

	public <R, T> SubCommonFilter<R, T> subQ(Class<R> root, Class<T> target, String fieldType) {
		var subFilter = new SubCommonFilter<R, T>(this, root, target);
		subQuery.put(fieldType, subFilter);
		return subFilter;
	}

	@SuppressWarnings("unchecked")
	public <R, T> SubCommonFilter<R, T> subQ(Field field, Class<T> target) {
		if (Collection.class.isAssignableFrom(field.getType())) {
			var root = (Class<R>) TypeUtils.getTypeArguments((ParameterizedType) field.getGenericType()).values()
					.stream().findFirst().get();
			return subQ(root, target, field.toString());
		}
		throw new JpaAmebaException("field type is not a Collection");
	}

	public List<FilterCondition> getList() {
		return list;
	}

	public List<SelectCondition> getSelectors() {
		return selectors;
	}

	public List<JoinCondition> getJoinList() {
		return joinList;
	}

	public List<OrderCondition> getOrderList() {
		return orderList;
	}

	public List<GroupCondition> getGroupingBy() {
		return groupingBy;
	}

	public String getCountField() {
		return countField;
	}

	public <T> CommonFilter update(String field, T value) {
		updatableList.add(new Element<Object>(field, value));
		return this;
	}

	public List<Element<? extends Object>> getUpdatableList() {
		return updatableList;
	}

	public <T> CommonFilter eq(String field, T value) {
		if (checkNotNull(value)) {
			if (value instanceof SubCommonFilter) {
				var subF = (SubCommonFilter<?, ?>) value;
				list.add((query, builder, path) -> {
					return builder.equal(PathUtils.getPath(field, path), subCondition(query, builder, subF));
				});
			} else {
				list.add((query, builder, path) -> builder.equal(PathUtils.getPath(field, path), value));
			}
		}
		return this;
	}

	protected <R, T> Expression<T> subCondition(CommonAbstractCriteria criteria, CriteriaBuilder builder,
			SubCommonFilter<R, T> filter) {
		Subquery<T> subQ = criteria.subquery(filter.targetClazz);
		Root<R> subRoot = subQ.from(filter.rootClazz);
		DaoUtils.conditionHandle(filter, subQ, builder, subRoot);
		return filter.genSub(builder, subQ);
	}

	public CommonFilter eqFeild(String field1, String field2) {
		list.add((query, builder, path) -> builder.equal(PathUtils.getPath(field1, path),
				PathUtils.getPath(field2, path)));
		return this;
	}

	public <T> CommonFilter neq(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> value instanceof SubCommonFilter
					? builder.notEqual(PathUtils.getPath(field, path),
							subCondition(query, builder, (SubCommonFilter<?, ?>) value))
					: builder.notEqual(PathUtils.getPath(field, path), value));
		return this;
	}

	public CommonFilter neq(String field1, String Field2) {
		list.add((query, builder, path) -> builder.notEqual(PathUtils.getPath(field1, path),
				PathUtils.getPath(Field2, path)));
		return this;
	}

	@SuppressWarnings("unchecked")
	public CommonFilter like(String field, String value) {
		if (checkNotNull(value))
			list.add(
					(query, builder, path) -> builder.like((Expression<String>) PathUtils.getPath(field, path), value));
		return this;
	}

	public CommonFilter isNull(String field) {
		list.add((query, builder, path) -> builder.isNull(PathUtils.getPath(field, path)));
		return this;
	}

	public <T> CommonFilter isNotNull(String field) {
		list.add((query, builder, path) -> builder.isNotNull(PathUtils.getPath(field, path)));
		return this;
	}

	public <T> CommonFilter in(String field, Collection<T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> PathUtils.getPath(field, path).in(value.toArray()));
		return this;
	}

	public <T> CommonFilter notIn(String field, Collection<T> value) {
		list.add((query, builder, path) -> builder.not(PathUtils.getPath(field, path).in(value.toArray())));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<? super T>> CommonFilter lt(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThan((Expression<T>) PathUtils.getPath(field, path), value));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<? super T>> CommonFilter lt(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThan((Expression<T>) PathUtils.getPath(field, path),
					subCondition(query, builder, value)));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<T>> CommonFilter le(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThanOrEqualTo((Expression<T>) PathUtils.getPath(field, path),
					value));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<? super T>> CommonFilter le(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.lessThanOrEqualTo((Expression<T>) PathUtils.getPath(field, path),
					subCondition(query, builder, value)));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<T>> CommonFilter gt(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.greaterThan((Expression<T>) PathUtils.getPath(field, path),
					value));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<? super T>> CommonFilter gt(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.greaterThan((Expression<T>) PathUtils.getPath(field, path),
					subCondition(query, builder, value)));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<T>> CommonFilter ge(String field, T value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder
					.greaterThanOrEqualTo((Expression<T>) PathUtils.getPath(field, path), value));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<? super T>> CommonFilter ge(String field, SubCommonFilter<?, T> value) {
		if (checkNotNull(value))
			list.add((query, builder, path) -> builder.greaterThanOrEqualTo(
					(Expression<T>) PathUtils.getPath(field, path), subCondition(query, builder, value)));
		return this;
	}

	@SuppressWarnings("unchecked")
	public <T extends Comparable<T>> CommonFilter between(String field, T minValue, T maxValue) {
		if (checkNotNull(maxValue) && checkNotNull(minValue)) {
			list.add((query, builder, path) -> builder.between((Path<T>) PathUtils.getPath(field, path), minValue,
					maxValue));
		}
		return this;
	}

	public CommonFilter orderByAsc(String... fields) {
		Stream.of(fields).forEach(x -> orderList.add((builder, path) -> builder.asc(PathUtils.getPath(x, path))));
		return this;
	}

	public CommonFilter orderByDesc(String... fields) {
		Stream.of(fields).forEach(x -> orderList.add((builder, path) -> builder.desc(PathUtils.getPath(x, path))));
		return this;
	}

	public CommonFilter orderBy(String field, OrderEnum orderEnum) {
		if (field == null)
			return this;
		orderList.add((builder, path) -> orderEnum == OrderEnum.ASC ? builder.asc(PathUtils.getPath(field, path))
				: builder.desc(PathUtils.getPath(field, path)));
		return this;
	}

	public CommonFilter groupBy(String... fields) {
		Stream.of(fields).forEach(x -> groupingBy.add((path) -> PathUtils.getPath(x, path)));
		return this;
	}

	public CommonFilter select(String... selectField) {
		for (String field : selectField)
			selectors.add((builder, path) -> PathUtils.getPath(field, path));
		return this;
	}

	public CommonFilter select(SelectCondition... elements) {
		for (SelectCondition selectElement : elements) {
			selectors.add(selectElement);
		}
		return this;
	}

	public CommonFilter select(Collection<String> selectField) {
		select(selectField.toArray(new String[] {}));
		return this;
	}

	public CommonFilter innerJoin(String field) {
		joinList.add((root) -> root.join(field, JoinType.INNER));
		return this;
	}

	public CommonFilter leftJoin(String table) {
		joinList.add(root -> root.join(table, JoinType.LEFT));
		return this;
	}

	public CommonFilter rightJoin(String table) {
		joinList.add(root -> root.join(table, JoinType.RIGHT));
		return this;
	}

	public CommonFilter limit(int limit) {
		Assert.isTrue(limit > 0, "limit count must larger then 0");
		limitStart = 0;
		limitCount = limit;
		return this;
	}

	public CommonFilter limit(int limitStart, int limitCount) {
		Assert.isTrue(limitCount > 0, "limit count must larger then 0");
		this.limitStart = limitStart;
		this.limitCount = limitCount;
		return this;
	}

	public <T> CommonFilter forQuery(T queryObj) {
		if (queryObj == null)
			return this;
		var clazz = queryObj.getClass();
		Stream.of(FieldUtils.getAllFields(clazz)).filter(x -> x.isAnnotationPresent(WhereCondition.class))
				.forEach(x -> {
					x.setAccessible(true);
					var fieldName = x.getName();
					try {
						var val = x.get(queryObj);
						if (val == null)
							return;
						var condition = x.getDeclaredAnnotation(WhereCondition.class);
						var subQuery = x.getDeclaredAnnotation(SubQuery.class);
						fieldName = StringUtils.isBlank(condition.value()) ? fieldName : condition.value();
						if (subQuery != null) {
							var subCommonFilter = subQ(subQuery.root(), subQuery.target(), fieldName)
									.select(subQuery.value());
							val = subCommonFilter.forQuery(val);
						}
						var filterSy = condition.condition();
						filterSy.getConditionFilter().filter(this, fieldName, val);
						return;

					} catch (IllegalArgumentException | IllegalAccessException e) {
						logger.warn("can not get query Field Value: " + fieldName, e);
					}
				});

		return this;
	}

	public boolean hasLimit() {
		return this.limitStart != null;
	}

	/**
	 * @return the limitCount
	 */
	public Integer getLimitCount() {
		return limitCount;
	}

	/**
	 * @param limitCount the limitCount to set
	 */
	public void setLimitCount(Integer limitCount) {
		this.limitCount = limitCount;
	}

	/**
	 * @return the limitStart
	 */
	public Integer getLimitStart() {
		return limitStart;
	}

	/**
	 * @param limitStart the limitStart to set
	 */
	public void setLimitStart(Integer limitStart) {
		this.limitStart = limitStart;
	}

	public CommonFilter pageCountField(String field) {
		this.countField = field;
		return this;
	}
}
